#ifndef TDC_V767_h
#define TDC_V767_h

extern "C" {
#include <sys/types.h>
#include "sis3100_vme_calls.h"
#include <stdio.h>
#include <unistd.h>
}

#include <sstream>

using namespace std;

#include "VmeModule.h"
#include "IOError.h"
#include "FIFO32DMAReader.h"

class TDC_V767 : public VmeModule, public FIFO32DMAReader
{
 public:
  //number of retries for hand shake
  const static int TIMEOUT         = 1000000;

  // patterns, to recognise
	const static u_int32_t TYPE_MASK = 0x00600000;
	const static u_int32_t EN_MASK   = 0x00000fff;

  const static u_int32_t HEADER    = 0x00400000; //header
  const static u_int32_t EOB       = 0x00200000; //end of block
  const static u_int32_t IV_DATA   = 0x00600000; //invalid data
	const static u_int32_t VALID_DATA= 0x00000000; //valid data

	const static u_int32_t EOB_STATUS_MASK  = 0x07000000;
	const static u_int32_t EOB_STATUS_ERROR = 0x01000000;
	const static u_int32_t EOB_EVENT_DATA_COUNTER = 0x0000ffff;

  const static int OUTBUF_OFFSET   = 0x0000; //Output Buffer Offset
  const static int SR1_OFFSET      = 0x000E; //Status Register 1 offset
  const static int SSRESET_OFFSET  = 0x0018; //Single Shot RESET register offset
  const static int SR2_OFFSET      = 0x0048; //Status Register 2 offset
  const static int EC_OFFSET       = 0x004c; //Event Counter register offset
  const static int CLEAR_EC_OFFSET = 0x004e; //Clear EventCounter reg. offset
  const static int OPC_HS_OFFSET   = 0x0050; //OPCode HandShake reg. offset 
  const static int OPC_OFFSET      = 0x0052; //OPCode register offset
  const static int CLEAR_OFFSET    = 0x0054; //Clear register offset

  // op codes, see tdc handbook
  const static u_int16_t STOP_MATCH     = 0x1000;
  const static u_int16_t START_MATCH    = 0x1100;
  const static u_int16_t START_GAT      = 0x1200;
  const static u_int16_t CONT_STO       = 0x1300;
  const static u_int16_t READ_ACQ_MOD   = 0x1400;
	const static u_int16_t EN_CHANNEL     = 0x2000;
	const static u_int16_t DIS_CHANNEL    = 0x2100;
  const static u_int16_t EN_ALL_CH      = 0x2300;
  const static u_int16_t DIS_ALL_CH     = 0x2400;
  const static u_int16_t SET_WIN_WIDTH  = 0x3000;
  const static u_int16_t READ_WIN_WIDTH = 0x3100;
  const static u_int16_t SET_WIN_OFFS   = 0x3200;
  const static u_int16_t READ_WIN_OFFS  = 0x3300;
	const static u_int16_t EN_SUB_TRG     = 0x3600;
	const static u_int16_t DIS_SUB_TRG    = 0x3700;
	const static u_int16_t RISE_ALL       = 0x6000;
  const static u_int16_t DR_EV_READY    = 0x7000;
  const static u_int16_t READ_TDC_ERR   = 0x8000;

	const static u_int16_t STATUS1_DREADY = 0x0001;

  /// Constructor
  TDC_V767( int handle, unsigned int address )
    : VmeModule( handle, address ) {}

  // the manual says: "A dummy acces to this register [at 0x0018] allows to
  //                   generate a single shot RESET of the module. Once issued,
  //                   the module Front End is reset"
  void reset() throw(IOError)
  {
    int r;

    // I think it doesn't matter what in particular is written,
    // so 0xaffe seems to suit well.
    if( (r=vme_A32D16_write(handle,address+SSRESET_OFFSET,0xaffe)) != 0 )
      throw IOError("TDC_V767::reset():\n"
		    "  vme_A32D16_write() returned non zero",r);  
  }

  /// tdcs are cleared, output buffer is cleared,
  /// readout controller is reset, event counter is set to 0
  void clearRegisters() throw(IOError)
  {  
    int r;

    // I think it doesn't matter what in particular is written,
    // so 0xaffe seems to suit well.
    if( (r=vme_A32D16_write(handle,address+CLEAR_OFFSET,0xaffe)) != 0 )
      throw IOError("TDC_V767::clearRegisters():\n"
		    "  vme_A32D16_write() returned non zero",r);  
  }

  /// write an op code to the microcontroller
  void writeOp(u_int16_t op) throw (IOError)
  {
    int r;
    u_int16_t hs;
    int time;

    // checks the Write OK bit
    time=0;
    do {
      if( (r=vme_A32D16_read(handle,address+OPC_HS_OFFSET,&hs)) != 0 )
	throw IOError("TDC_V767::writeOP():\n"
		      "  vme_A32D16_read() returned non zero",r);
      time++;
    } while( (hs != 0x02) && (time < TIMEOUT) );

    if(time >= TIMEOUT) {
	throw IOError("TDC_V767::writeOP(): time out error",time);
    }

    usleep(10000); // manual says this is necessary

    // write the op code
    if( (r=vme_A32D16_write(handle,address+OPC_OFFSET,op)) != 0 )
      throw IOError("TDC_V767::writeOP():\n"
		    "vme_A32D16_write() returned non zero",r);    
  }

  /// read an op code from the microcontroller
  u_int16_t readOp() throw(IOError)
  {
    int r;
    u_int16_t hs,op;
    int time;

    // wait until new read / write opcode operation is ready
    time=0;
    do {
      if( (r=vme_A32D16_read(handle,address+OPC_HS_OFFSET,&hs)) != 0 )
	throw IOError("TDC_V767::writeOP():\n"
		      "vme_A32D16_read() returned non zero",r);
      time++;
    } while( (hs != 0x01) && (time < TIMEOUT) );

    if(time >= TIMEOUT) {
	throw IOError("TDC_V767::readOP(): time out error",time);
    }

    usleep(10000); // manual says this is necessary

    // read the op code
    if( (r=vme_A32D16_read(handle,address+OPC_OFFSET,&op)) != 0 )
      throw IOError("TDC_V767::writeOP():\n"
		    "vme_A32D16_read() returned non zero",r);    

    return op;
  }

  /// these are a dummy functions, you can use writeOp() directly...
  void setMode( u_int16_t mode ) throw (IOError)
  {
      try {
	  writeOp(mode);
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::setMode():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }  
  u_int16_t getMode() throw (IOError)
  {
      u_int16_t r;
      try {
	  writeOp(READ_ACQ_MOD);
	  r=readOp();
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::getMode():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
    return r;
  }  
  void setWindowWidth(u_int16_t width) throw(IOError)
  {
      try {
	  writeOp(SET_WIN_WIDTH);
	  writeOp(width);
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::setWindowWidth():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  u_int16_t getWindowWidth() throw(IOError)
  {
      u_int16_t r;
      try {
	  writeOp(READ_WIN_WIDTH);
	  r=readOp();
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::getWindowWidth():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
      return r;
  }
  void setWindowOffset(u_int16_t offset) throw(IOError)
  {
      try {
	  writeOp(SET_WIN_OFFS);
	  writeOp(offset);
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::setWindowOffset():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  u_int16_t getWindowOffset() throw (IOError)
  {
      u_int16_t r;
      try {
	  writeOp(READ_WIN_OFFS);
	  r=readOp();
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::getWindowOffset():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
      return r;
  }
  void setDataReady() throw (IOError)
  {
      try {
	  writeOp(DR_EV_READY);
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::setDataReady():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  void enableAllChannels() throw (IOError)
  {
      try {
	  writeOp(EN_ALL_CH);
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::enableAllChannels():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  void enableChannel(u_int8_t ch) throw (IOError)
  {
      try {
					writeOp(EN_CHANNEL | (ch & 0x3f));
      } catch(IOError ioe) {
					stringstream ss;
					ss << "TDC_V767::enableChannel():\n " << ioe.getMsg();
					throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  void disableChannel(u_int8_t ch) throw (IOError)
  {
      try {
					writeOp(DIS_CHANNEL | (ch & 0x3f));
      } catch(IOError ioe) {
					stringstream ss;
					ss << "TDC_V767::disableChannel():\n " << ioe.getMsg();
					throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  void disableAllChannels() throw (IOError)
  {
      try {
	  writeOp(DIS_ALL_CH);
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::disableAllChannels():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  void enSubTriggerTime() throw (IOError)
  {
      try {
					writeOp(EN_SUB_TRG);
      } catch(IOError ioe) {
					stringstream ss;
					ss << "TDC_V767::enSubTriggerTime():\n " << ioe.getMsg();
					throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  void disSubTriggerTime() throw (IOError)
  {
      try {
	  writeOp(DIS_SUB_TRG);
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::disSubTriggerTime():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  void setRisingEdgeAll() throw (IOError)
  {
      try {
					writeOp(RISE_ALL);
      } catch(IOError ioe) {
					stringstream ss;
					ss << "TDC_V767::setRisingEdgeAll():\n " << ioe.getMsg();
					throw IOError(ss.str().c_str(),ioe.getParam());
      }
  }
  u_int16_t readTDCErrorCode(u_int8_t tdc_no)
  {
      if(tdc_no>1)
	  throw IOError("TDC_V767::readTDCErrorCode():\n"
			"  invalid tdc number, must be 0 or 1",tdc_no);

      u_int16_t r;
      try {
	  writeOp(READ_TDC_ERR | (u_int16_t)tdc_no);
	  r=readOp();
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::readTDCErrorCode():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }

      return r;
  }

  u_int16_t readEventCounter() throw (IOError)
  {
    u_int16_t r,counter;

    if( (r=vme_A32D16_read(handle,address+EC_OFFSET,&counter)) != 0 )
	throw IOError("TDC_V767::readEventCounter():\n"
		      "  vme_A32D16_read() returned non zero",r);
    
    return counter;
  } 


  void clearEventCounter() throw (IOError)
  {
    u_int16_t r;

    // I think it doesn't matter what in particular is written,
    // so 0xaffe seems to suit well.
    if( (r=vme_A32D16_write(handle,address+CLEAR_EC_OFFSET,0xaffe)) )
	throw IOError("TDC_V767::clearEventCounter():\n"
		      "  vme_A32D16_write() returned non zero",r);
  }


  /// returns contents of the Status Register 1
  u_int16_t getStatus1() throw (IOError)
  {
    u_int16_t r,status;

    if( (r=vme_A32D16_read(handle,address+SR1_OFFSET,&status)) != 0 )
				throw IOError("TDC_V767::getStatus1():\n"
											"  vme_A32D16_read() returned non zero",r);
    
    return status;    
  }


  /// returns contents of the Status Register 2
  u_int16_t getStatus2() throw (IOError)
  {
    u_int16_t r,status;

    if( (r=vme_A32D16_read(handle,address+SR2_OFFSET,&status)) != 0 )
	throw IOError("TDC_V767::getStatus2():\n"
		      "  vme_A32D16_read() returned non zero",r);
    
    return status;
  }

  void readEvent(u_int16_t *e_number,u_int32_t *buf, u_int32_t bufsize)
    throw (IOError)
  {
      u_int16_t r;
      u_int32_t d;
      
      if(!bufsize)
	  throw IOError("TDC_V767::readEvent():\n"
			"  invalid bufsize (=0)",bufsize);

      // check if data ready
      u_int16_t s1;
      try {
	  s1=getStatus1();
      } catch(IOError ioe) {
	  stringstream ss;
	  ss << "TDC_V767::readEvent():\n " << ioe.getMsg();
	  throw IOError(ss.str().c_str(),ioe.getParam());
      }

      if( !(s1 & 0x0001) )
	  throw IOError("TDC_V767::readEvent():\n"
			"  status register 1:   data ready = 0",s1);

      //read header
      if( (r=vme_A32D32_read(handle,address+OUTBUF_OFFSET,&d)) != 0 )
	  throw IOError("TDC_V767::readEvent():\n"
			"  vme_A32D32_read() return non zero",r);
      if( !((d & 0x00600000)==HEADER) )
	  throw IOError("TDC_V767::readEvent():\n"
			"  header expected",d);
      *e_number = (u_int16_t)d & 0x0fff;

      u_int32_t i=0;
      do {
	  if( (r=vme_A32D32_read(handle,address+OUTBUF_OFFSET,&d)) != 0 )
	      throw IOError("TDC_V767::readEvent():\n"
			    "  vme_A32D32_read() return non zero",r);

	  if( (d & 0x00600000) == IV_DATA )
	      throw IOError("TDC_V767::readEvent():\n"
			    "  invalid data",r);	
	  
	  if( (d & 0x00600000) == EOB ) {
	      if(d & 0x01000000)
		  throw IOError("TDC_V767::readEvent():\n"
				"  TDC chip error",r);
	      
	      *e_number = (u_int16_t)d;
	
	      break;
	  }


	  if( i >= bufsize )
	      throw IOError("TDC_V767::readEvent():\n"
			    "  to many time values for buffer",i);

	  buf[i] = d;
	  i++;
      } while(1);
  }

  u_int32_t readFIFO32DMA(u_int32_t num, u_int32_t *buf)
  {
    if(!num)
      throw IOError("TDC_V767::readFIFO32DMA():\n"
		    "  invalid number of words (=0)",num);

		

    // check if data ready
    u_int16_t s1;
	  int MAX_RETRIES=20;           //do 20 retries
		int retries=MAX_RETRIES; 

		do {
				try { s1=getStatus1(); }
				catch(IOError ioe) {
						stringstream ss;
						ss << "TDC_V767::readFIFO32DMA():\n " << ioe.getMsg();
						throw IOError(ss.str().c_str(),ioe.getParam());
				}
				
				if( s1 & STATUS1_DREADY ) break;
				usleep(50);
				retries--;
		} while( retries );

    if( !(s1 & STATUS1_DREADY) ) {
				stringstream ss;
				ss << "TDC_V767::readFIFO32DMA(): dataready=0 (statusregister1) "
					 <<	"retries=" << (MAX_RETRIES-retries);
				throw IOError( ss.str().c_str(), s1 );
		}

    int r; u_int32_t read;
    if( (r=vme_A32DMA_D32FIFO_read(handle,address+OUTBUF_OFFSET,
																	 buf,num,&read)) != 0 )
				throw IOError("TDC_V767::readFIFO32DMA():\n"
								"  vme_A32DMA_D32FIFO_read() return non zero",r);
		
    return read;
  }

	u_int32_t readWord()
	{
			int r; u_int32_t d;
			
	  int MAX_RETRIES=20;           //do 20 retries
		int retries=MAX_RETRIES; 

		u_int16_t s1;
		do {
				try { s1=getStatus1(); }
				catch(IOError ioe) {
						stringstream ss;
						ss << "TDC_V767::readWord():\n " << ioe.getMsg();
						throw IOError(ss.str().c_str(),ioe.getParam());
				}
				
				if( s1 & STATUS1_DREADY ) break;
				usleep(50);
				retries--;
		} while( retries );
    if( !(s1 & STATUS1_DREADY) ) {
				stringstream ss;
				ss << "TDC_V767::readWord(): dataready=0 (statusregister1) "
					 <<	"retries=" << (MAX_RETRIES-retries);
				throw IOError( ss.str().c_str(), s1 );
		}

		if( (r=vme_A32D32_read(handle,address+OUTBUF_OFFSET,
																	 &d)) != 0 )
				throw IOError("TDC_V767::readWord():\n"
											"  vme_A32D32_read() return non zero",r);

		return d;
	}
};

#endif
